/*
    TwoPageView image viewer
    Copyright (C) 2006 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

namespace TwoPageView
{
    public delegate bool ProgressReportAndQueryCancel(string actualImageFilePath, string thumbnailCacheAddFilePath, long fileLength, int progressPercentage);
    public sealed class DirectoryOrZipDirInfo 
    {
        private DirectoryInfo di;
        private bool exists;
        private bool inflatedAllFiles;
        private bool inflatedAllFolders = true;
        private string fullName = string.Empty;
        private string tmpDirName = string.Empty;
        
        private static string prefixOfTmpFolderPath = string.Empty;
        public static void SetPrefixOfTmpFolderPath(string path, bool ifSetCustomPath)
        {
            prefixOfTmpFolderPath = ifSetCustomPath ? path : string.Empty;
        }
        public static string PrefixOfTmpFolderPath() {
            return (0 == prefixOfTmpFolderPath.Length) ?
                Path.GetTempPath() : (prefixOfTmpFolderPath + Path.DirectorySeparatorChar);
        }

        private static Dictionary<string, DirectoryOrZipDirInfo> pathNameToZipDirInfo =
            new Dictionary<string, DirectoryOrZipDirInfo>();

        /// <summary>
        /// readerwriterlock for pathNameToZipDirInfo
        /// </summary>
        private static System.Threading.ReaderWriterLock pathNameToZipDirInfoLock = new System.Threading.ReaderWriterLock();
        private System.DateTime lastWriteTime;
        private List<string> droppedEntries = null;

        private static ProgressReportAndQueryCancel progressReportAndQueryCancel;

        public static void ProgressReportAndQueryCancelDelegateSet(ProgressReportAndQueryCancel func)
        {
            progressReportAndQueryCancel = func;
        }

        private static DirectoryOrZipDirInfo ZipDirInfoFindFromFolderName(string path, bool matchWithExactSamePath)
        {
            int matchLen = 0;
            DirectoryOrZipDirInfo zipDirInfo = null;
            pathNameToZipDirInfoLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            foreach (KeyValuePair<string, DirectoryOrZipDirInfo> kvp in pathNameToZipDirInfo) {
                if (path.StartsWith(kvp.Key, StringComparison.Ordinal)) {
                    if (!matchWithExactSamePath && path.Equals(kvp.Key)) {
                        continue;
                    }
                    if (matchLen < kvp.Key.Length) {
                        matchLen = kvp.Key.Length;
                        zipDirInfo = kvp.Value;
                    }
                }
            }
            pathNameToZipDirInfoLock.ReleaseReaderLock();
            return zipDirInfo;
        }

        // return pathname which contains actual zip file
        private static string ZipFilePathTranslate(string path, bool matchWithExactSamePath)
        {
            string result = path;
            DirectoryOrZipDirInfo zipDirInfo = ZipDirInfoFindFromFolderName(path, matchWithExactSamePath);
            if (null != zipDirInfo && !path.Equals(zipDirInfo.fullName)) {
                result = result.Replace(zipDirInfo.fullName, zipDirInfo.tmpDirName);
            }
            return result;
        }

        private static string ZipInflateFolderNameFind(string zipPath)
        {
            string result = string.Empty;
            pathNameToZipDirInfoLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            if (pathNameToZipDirInfo.ContainsKey(zipPath)) {
                result = pathNameToZipDirInfo[zipPath].tmpDirName;
            }
            pathNameToZipDirInfoLock.ReleaseReaderLock();
            return result;
        }

        public static void RemoveTmpDirs()
        {
            pathNameToZipDirInfoLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            Console.WriteLine("D: RemoveTmpDirs() count={0}", pathNameToZipDirInfo.Count);
            foreach (KeyValuePair<string, DirectoryOrZipDirInfo> kvp in pathNameToZipDirInfo) {
                try {
                    Console.WriteLine("D: RemoveTmpDirs() {0}", kvp.Value.tmpDirName);
                    if (Directory.Exists(kvp.Value.tmpDirName)) {
                        Directory.Delete(kvp.Value.tmpDirName, true);
                    }
                } catch (IOException) {
                }
            }
            pathNameToZipDirInfo.Clear();
            pathNameToZipDirInfoLock.ReleaseWriterLock();
        }

        /// <summary>
        /// check if path suffix is .zip or .lzh
        /// </summary>
        /// <param name="path">path to check</param>
        /// <returns>true: path has archive suffix</returns>
        private static bool HasArchiveFileSuffix(string path)
        {
            if (path.EndsWith(".zip", true, CultureInfo.CurrentCulture)
             || path.EndsWith(".lzh", true, CultureInfo.CurrentCulture)) {
                return true;
            }
            return false;
        }
        public bool IsDragDropDir
        {
            get { return null != droppedEntries; }
        }
        public bool IsZipDir
        {
            get {
                string translatedPathName = ZipFilePathTranslate(fullName, false);
                if (!File.Exists(translatedPathName)) {
                    return false;
                }
                return HasArchiveFileSuffix(fullName);
            }
        }
        public bool IsPdfDir
        {
            get {
                string translatedPathName = ZipFilePathTranslate(fullName, false);
                if (!File.Exists(translatedPathName)) {
                    return false;
                }
                return fullName.EndsWith(".pdf", true, CultureInfo.CurrentCulture);
            }
        }

        /// <summary>
        /// check path is directory-like entry (directory or zip file or lzh file or pdf file)
        /// </summary>
        /// <param name="path">path to check</param>
        /// <returns>true: path is directory-like entry</returns>
        public static bool IsDirectoryOrZipfileOrPdfFile(string path)
        {
            if (null == path) {
                throw new ArgumentNullException("path");
            }
            if (Directory.Exists(path) ||
                path.EndsWith(".zip", true, CultureInfo.CurrentCulture) ||
                path.EndsWith(".lzh", true, CultureInfo.CurrentCulture) ||
                path.EndsWith(".pdf", true, CultureInfo.CurrentCulture)) {
                return true;
            }
            return false;
        }

        public bool Exists
        {
            get { return exists; }
        }
        public bool IsSubDirExists()
        {
            if (IsDragDropDir) {
                return true;
            }
            if (IsZipDir) {
                return true;
            }
            if (IsPdfDir) {
                return false;
            }
            if (null == di) {
                return false;
            }
            DirectoryInfo[] subDirs = di.GetDirectories();
            if (0 < subDirs.Length) {
                return true;
            }
            FileInfo[] files = di.GetFiles();
            foreach (FileInfo fi in files) {
                if (HasArchiveFileSuffix(fi.Name)) {
                    return true;
                }
                if (0 == string.Compare(fi.Extension, ".pdf", StringComparison.OrdinalIgnoreCase)) {
                    return true;
                }
            }
            return false;
        }
        public void Refresh()
        {
            if (null != di) {
                di.Refresh();
            }
        }
        public string Name
        {
            get {
                if (IsDragDropDir) {
                    return fullName;
                }
                return Path.GetFileName(fullName);
            }
        }
        public string FullName
        {
            get { return fullName; }
            set { fullName = value; }
        }

        private DirectoryOrZipDirInfo(string path)
        {
            fullName = path;
            tmpDirName = ZipInflateFolderNameFind(fullName);
            string translatedPathName = ZipFilePathTranslate(fullName, true);
            //Console.WriteLine("D: DirectoryOrZipDirInfo ctor {0} -> {1}", path, translatedPathName);
            if (File.Exists(translatedPathName) && HasArchiveFileSuffix(translatedPathName)) {
                exists = true;
                inflatedAllFolders = false;
                lastWriteTime = File.GetLastWriteTime(translatedPathName);
                return; //< unzip later...
            }
            if (File.Exists(translatedPathName) && translatedPathName.EndsWith(".pdf", true, CultureInfo.CurrentCulture)) {
                exists = true;
                lastWriteTime = File.GetLastWriteTime(translatedPathName);
                return; //< extract jpeg later...
            }
            di = new DirectoryInfo(translatedPathName);
            if (null != di && di.Exists) {
                exists = true;
            }
        }

        /// <summary>
        /// ctor for drag drop dirInfo
        /// </summary>
        private DirectoryOrZipDirInfo()
        {
            droppedEntries = new List<string>();
            fullName = string.Empty;
            tmpDirName = string.Empty;
            exists = true;
            inflatedAllFiles = true;
        }

        private static DirectoryOrZipDirInfo DragDropDirInfoFactory(string[] paths)
        {
            Debug.Assert(0 < paths.Length);
            DirectoryOrZipDirInfo x = new DirectoryOrZipDirInfo();

            for (int i=0; i<paths.Length; ++i) {
                if (!x.droppedEntries.Contains(paths[i])) {
                    x.droppedEntries.Add(paths[i]);
                }
            }
            return x;
        }

        public static DirectoryOrZipDirInfo Factory(string path)
        {
            DirectoryOrZipDirInfo x = ZipDirInfoFindFromFolderName(path, true);
            if (null == x || !x.fullName.Equals(path)) {
                x = new DirectoryOrZipDirInfo(path);
            }
            return x;
        }

        public static DirectoryOrZipDirInfo Factory(string[] paths)
        {
            if (null == paths) {
                throw new ArgumentNullException("paths");
            }
            DirectoryOrZipDirInfo d = null;
            if (1 == paths.Length) {
                string path = paths[0];
                if (IsDirectoryOrZipfileOrPdfFile(path)) {
                    d = Factory(path);
                } else {
                    d = DragDropDirInfoFactory(paths);
                }
            } else if (2 <= paths.Length) {
                d = DragDropDirInfoFactory(paths);
            }
            return d;
        }

        // called from the UI thread
        public DirectoryOrZipDirInfo[] GetDirectories()
        {
            if (IsDragDropDir) {
                return null;
            }
            if (IsPdfDir) {
                return null;
            }
            UnzipAllFolders();
            if (null == di) {
                return null;
            }
            List<DirectoryOrZipDirInfo> subDirList = 
                new List<DirectoryOrZipDirInfo>();

            DirectoryInfo[] subDirs = di.GetDirectories();
            foreach (DirectoryInfo d in subDirs) {
                DirectoryOrZipDirInfo x = DirectoryOrZipDirInfo.Factory(MyUtil.PathNormalize(fullName + Path.DirectorySeparatorChar + d.Name));
                subDirList.Add(x);
            }
            FileInfo[] files = di.GetFiles();
            foreach (FileInfo fi in files) {
                if (HasArchiveFileSuffix(fi.Name)) {
                    DirectoryOrZipDirInfo x = DirectoryOrZipDirInfo.Factory(MyUtil.PathNormalize(fullName + Path.DirectorySeparatorChar + fi.Name));
                    if (x.Exists) {
                        subDirList.Add(x);
                    }
                }
                if (0 == string.Compare(fi.Extension, ".pdf", StringComparison.OrdinalIgnoreCase)) {
                    DirectoryOrZipDirInfo x = DirectoryOrZipDirInfo.Factory(MyUtil.PathNormalize(fullName + Path.DirectorySeparatorChar + fi.Name));
                    if (x.Exists) {
                        subDirList.Add(x);
                    }
                }
            }
            return subDirList.ToArray();
        }

        // called from the background thread
        private FileInfo[] DragDropGetFiles()
        {
            List<FileInfo> subFileList =
                new List<FileInfo>();

            foreach (string s in droppedEntries) {
                if (!IsDirectoryOrZipfileOrPdfFile(s)) {
                    FileInfo fi = new FileInfo(s);
                    subFileList.Add(fi);
                }
            }
            return subFileList.ToArray();
        }

        // called from the background thread
        public FileInfo[] GetFiles()
        {
            if (IsDragDropDir) {
                return DragDropGetFiles();
            }
            if (IsPdfDir) {
                PdfExtractJpeg();
            } else {
                UnzipAllFiles();
            }
            if (null == di) {
                return null;
            }
            FileInfo[] result = null;
            try {
                result = di.GetFiles();
            } catch (DirectoryNotFoundException) {
                result = null;
            }
            return result;
        }

        // called from the background thread
        private void PdfExtractJpeg()
        {
            if (inflatedAllFiles) {
                return;
            }
            pathNameToZipDirInfoLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            if (0 == tmpDirName.Length) {
                tmpDirName = DirectoryOrZipDirInfo.PrefixOfTmpFolderPath() + 
                       Path.GetRandomFileName();
                Directory.CreateDirectory(tmpDirName);
                di = new DirectoryInfo(tmpDirName);
                pathNameToZipDirInfo.Add(fullName, this);
            }
            pathNameToZipDirInfoLock.ReleaseWriterLock();
            string translatedPathName = ZipFilePathTranslate(fullName, true);
            if (!PdfExtractImagesToFolder(translatedPathName, tmpDirName)) {
                if (Directory.Exists(tmpDirName)) {
                    Directory.Delete(tmpDirName, true);
                }
                return;
            }
            di.Refresh();
            inflatedAllFiles = true;
        }

        // called from the background thread
        private void UnzipAllFiles()
        {
            {   // If parent directory is zip file, unzip it first.
                DirectoryOrZipDirInfo parentDi = ZipDirInfoFindFromFolderName(fullName, false);
                if (null != parentDi) {
                    parentDi.UnzipAllFiles();
                }
            }
            if (!IsZipDir) {
                return;
            }
            UnzipAllFolders();
            if (inflatedAllFiles) {
                return;
            }
            pathNameToZipDirInfoLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            string translatedPathName = ZipFilePathTranslate(fullName, false);
            if (0 == tmpDirName.Length) {
                tmpDirName = DirectoryOrZipDirInfo.PrefixOfTmpFolderPath() + 
                     Path.GetRandomFileName();
                Directory.CreateDirectory(tmpDirName);
                Directory.SetLastWriteTime(tmpDirName, lastWriteTime);
                di = new DirectoryInfo(tmpDirName);
                pathNameToZipDirInfo.Add(fullName, this);
            }
            pathNameToZipDirInfoLock.ReleaseWriterLock();
            if (!UnzipAllFilesToFolder(translatedPathName, tmpDirName)) {
                // aborted by cancel or error
                di.Refresh();
                return;
            }
            di.Refresh();
            inflatedAllFiles = true;
            inflatedAllFolders = true;
        }

        // called from the UI thread
        private void UnzipAllFolders()
        {
            pathNameToZipDirInfoLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            UnzipAllFolders1();
            pathNameToZipDirInfoLock.ReleaseWriterLock();
        }
        private void UnzipAllFolders1()
        {
            if (inflatedAllFolders) {
                return;
            }
            string translatedPathName = ZipFilePathTranslate(fullName, true);
            if (0 == tmpDirName.Length) {
                tmpDirName = DirectoryOrZipDirInfo.PrefixOfTmpFolderPath() + 
                    Path.GetRandomFileName();
                Directory.CreateDirectory(tmpDirName);
                Directory.SetLastWriteTime(tmpDirName, lastWriteTime);
                pathNameToZipDirInfo.Add(fullName, this);
                di = new DirectoryInfo(tmpDirName);
            }
            if (!UnzipAllFoldersToFolder(translatedPathName, tmpDirName)) {
                pathNameToZipDirInfo.Remove(fullName);
                Directory.Delete(tmpDirName, true);
                tmpDirName = string.Empty;
                di = null;
                return;
            }
            di.Refresh();
            inflatedAllFolders = true;
        }

        private static int FindNextStr(BinaryReader reader, string [] findStr)
        {
            if (findStr.Length == 0) {
                return -1;
            }
            for (int i=0; i < findStr.Length; ++i) {
                Debug.Assert(findStr[i].Length != 0);
            }

            var nMatchArray = new int[findStr.Length];

            while (true) {
                char c = (char)reader.ReadByte();

                for (int i=0; i < findStr.Length; ++i) {
                    if (c == findStr[i][nMatchArray[i]]) {
                        ++nMatchArray[i];
                        if (findStr[i].Length <= nMatchArray[i]) {
                            return i;
                        }
                    } else {
                        if (' ' == c || '\t' == c) {
                            continue;
                        }
                        nMatchArray[i] = 0;
                    }
                }
            }
        }

        private static void FindNextStr(BinaryReader reader, string findStr)
        {
            int nMatch = 0;

            while (nMatch < findStr.Length) {
                char c = (char)reader.ReadByte();
                if (c == findStr[nMatch]) {
                    ++nMatch;
                } else {
                    if (' ' == c || '\t' == c) {
                        continue;
                    }
                    nMatch = 0;
                }
            }
        }

        private static List<byte> ReadBytesTillTheString(BinaryReader reader, string findStr) {
            var byteList = new List<byte>();
            int nMatch = 0;

            while (nMatch < findStr.Length) {
                byte b = reader.ReadByte();
                byteList.Add(b);
                char c = (char)b;
                if (c == findStr[nMatch]) {
                    ++nMatch;
                } else {
                    if (' ' == c || '\t' == c) {
                        continue;
                    }
                    nMatch = 0;
                }
            }

            if (nMatch == findStr.Length) {
                byteList.RemoveRange(byteList.Count - findStr.Length, findStr.Length);
            }

            return byteList;
        }

        private static byte SkipWhitespaceAndCRLF(BinaryReader reader) {
            byte b = 0;
            do {
                b = reader.ReadByte();
            } while (Char.IsWhiteSpace((char)b) || b == 0x0a || b == 0x0d);

            return b;
        }

        private static void TrimCRLF(List<byte> byteList) {
            while (0 < byteList.Count) {
                var b = byteList[byteList.Count -1];
                if (b == 0x0a || b == 0x0d) {
                    byteList.RemoveAt(byteList.Count - 1);
                } else {
                    return;
                }
            }
        }

        private static long FileCreateAndCopyStream(string newFileName, long fileSize, Stream s)
        {
            if (LhaUtil.LhaEntry.TooLargeFileSize <= fileSize) {
                System.Console.WriteLine("E: FileCreateAndCopyStream fileSize too large {0}", fileSize);
                return 0;
            }
            long pos = 0;
            using (FileStream fs = File.Create(newFileName)) {
                byte[] data = new byte[4096];
                while (true) {
                    int size = s.Read(data, 0, data.Length);
                    if (size > 0) {
                        fs.Write(data, 0, size);
                        pos += size;
                    } else {
                        break;
                    }
                    if (fileSize < pos) {
                        System.Console.WriteLine("E: FileCreateAndCopyStream fileSize exceeded {0}", fileSize);
                        break;
                    }
                }
            }
            return pos;
        }

        private static long WriteToFile(byte[] content, string path)
        {
            using (FileStream fs = new FileStream(path, FileMode.Create)) {
                File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Temporary);
                using (BinaryWriter w = new BinaryWriter(fs)) {
                    w.Write(content);
                }
            }
            return content.Length;
        }

        /// <summary>
        /// read decimal number text from binary stream
        /// </summary>
        /// <returns>-1: fail</returns>
        private static int ReadDecimalText(BinaryReader reader)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            bool digitAppears = false;

            while (true) {
                char c = (char)reader.ReadByte();
                if (!digitAppears && Char.IsWhiteSpace(c)) {
                    continue;
                }
                if (!Char.IsDigit(c)) {
                    if (0 == sb.Length) {
                        return -1;
                    }
                    return System.Convert.ToInt32(sb.ToString(), CultureInfo.InvariantCulture);
                }

                digitAppears = true;
                sb.Append(c);
            }
        }

        private static string ReadOneWord(BinaryReader reader) {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            while (true) {
                char c = (char)reader.ReadByte();
                if (!Char.IsLetterOrDigit(c)) {
                    return sb.ToString();
                }
                sb.Append(c);
            }
        }

        private static bool PdfExtractImagesToFolder(string pdfFileName, string extractFolderName)
        {
            Console.WriteLine("D: PdfExtractToFolder({0}, {1})", pdfFileName, extractFolderName);
            bool result = true;
            int fileNumber = 0;

            //CCITTFaxDecode ccittFaxDecode = new CCITTFaxDecode();
            CCITTFaxToTIFF ccittFaxToTiff = new CCITTFaxToTIFF();

            try {
                System.DateTime lastWriteTime = File.GetLastWriteTime(pdfFileName);
                if (!Directory.Exists(extractFolderName)) {
                    Directory.CreateDirectory(extractFolderName);
                }
                using (FileStream fs = new FileStream(pdfFileName, FileMode.Open, FileAccess.Read)) {
                    using (BinaryReader r = new BinaryReader(fs)) {
                        System.Drawing.Size wh = new System.Drawing.Size();
                        int imageSize = -1;
                        string filterName = string.Empty;

                        while (result) {
                            FindNextStr(r, new string[] { "/Subtype/Image", "/BitsPerComponent"});
                            imageSize = -1;
                            wh.Width = -1;
                            wh.Height = -1;
                            filterName = string.Empty;

                            bool streamDone = false;
                            do {
                                var directive = FindNextStr(r, new string[] { "Filter/", "/Length ", "/Width", "/Height", "/Columns", "/Rows", "stream" });
                                switch (directive) {
                                case 0: // Filter/
                                    filterName = ReadOneWord(r);
                                    break;
                                case 1: // /Length
                                    imageSize = ReadDecimalText(r);
                                    break;
                                case 2: // /Width
                                    wh.Width = ReadDecimalText(r);
                                    break;
                                case 3: // /Height
                                    wh.Height = ReadDecimalText(r);
                                    break;
                                case 4: // /Columns
                                    wh.Width = ReadDecimalText(r);
                                    break;
                                case 5: // /Rows
                                    wh.Height = ReadDecimalText(r);
                                    break;
                                case 6: // stream
                                    string writePath;
                                    string fileName;
                                    long fileLength;

                                    var byteList = new List<byte>();
                                    byteList.Add(SkipWhitespaceAndCRLF(r));
                                    byteList.AddRange(ReadBytesTillTheString(r, "endstream"));
                                    TrimCRLF(byteList);

                                    if ("CCITTFaxDecode".Equals(filterName)) {
                                        fileName = string.Format(CultureInfo.InvariantCulture, "{0:D5}.tif", ++fileNumber);
                                        writePath = extractFolderName + Path.DirectorySeparatorChar + fileName;
                                        //ccittFaxDecode.CCITTFaxToBMP(b, wh, false, string.Format("{0}\\{1:D5}.bmp", extractFolderName, ++fileNumber));
                                        fileLength = ccittFaxToTiff.Convert(byteList.ToArray(), wh, writePath);
                                    } else {
                                        fileName = string.Format(CultureInfo.InvariantCulture, "{0:D5}.jpg", ++fileNumber);
                                        writePath = extractFolderName + Path.DirectorySeparatorChar + fileName;
                                        fileLength = WriteToFile(byteList.ToArray(), writePath);
                                    }
                                    File.SetLastWriteTime(writePath, lastWriteTime);
                                    string thumbnailCachePath = pdfFileName + Path.DirectorySeparatorChar + fileName;

                                    streamDone = true;
                                    if (null != progressReportAndQueryCancel && progressReportAndQueryCancel(writePath, thumbnailCachePath, fileLength, (int)(100 * fs.Position / fs.Length))) {
                                        result = false;
                                    }
                                    break;
                                default:
                                    Debug.Assert(false);
                                    break;
                                }
                            } while (!streamDone);
                        }
                    }
                }
            } catch (EndOfStreamException) {
            } catch (IOException ex) {
                Console.WriteLine(ex);
                result = false;
            } catch (System.UnauthorizedAccessException) {
                result = false;
            }
            return result;
        }

        /// <summary>
        /// Unzip file or folder from archive file zipFileName to toFolderName
        /// </summary>
        /// <param name="zipFileName"></param>
        /// <param name="toFolderName"></param>
        /// <param name="bUnzipFile">true: unzip file and folder false: unzip folder only</param>
        /// <returns>unzip aborted by cancelled or by error</returns>
        private static bool UnzipAllFilesToFolder(string zipFileName, string toFolderName, bool bUnzipFile)
        {
            if (null == zipFileName) {
                throw new ArgumentNullException("zipFileName");
            }
            if (null == toFolderName) {
                throw new ArgumentNullException("toFolderName");
            }
            Console.WriteLine("D: UnzipAllFilesToFolder({0}, {1}, {2})", zipFileName, toFolderName, bUnzipFile);
            bool result = true;
            int nFolders = 0;

            if (zipFileName.EndsWith(".zip", true, CultureInfo.CurrentCulture)) {
                try {
                    System.DateTime lastWriteTime = File.GetLastWriteTime(zipFileName);
                    long fileSize = 1;
                    {
                        FileInfo fi = new FileInfo(zipFileName);
                        fileSize = fi.Length;
                    }
                    using (ICSharpCode.SharpZipLib.Zip.ZipInputStream s =
                        new ICSharpCode.SharpZipLib.Zip.ZipInputStream(File.OpenRead(zipFileName))) {
                        ICSharpCode.SharpZipLib.Zip.ZipEntry theEntry;
                        while ((theEntry = s.GetNextEntry()) != null) {
                            string directoryName = Path.GetDirectoryName(theEntry.Name);
                            System.Text.StringBuilder writeFolderNameBuilder = new System.Text.StringBuilder(toFolderName);
                            if (directoryName.Length > 0) {
                                writeFolderNameBuilder.Append(Path.DirectorySeparatorChar);
                                writeFolderNameBuilder.Append(directoryName);
                            }
                            string writeFolderName = writeFolderNameBuilder.ToString();
                            if (!Directory.Exists(writeFolderName)) {
                                Directory.CreateDirectory(writeFolderName);
                                ++nFolders;
                            }
                            if (!bUnzipFile) {
                                continue;
                            }
                            string fileName = Path.GetFileName(theEntry.Name);
                            if (0 == fileName.Length) {
                                continue;
                            }
                            string writePath = writeFolderName + Path.DirectorySeparatorChar + fileName;

                            long fileLength = FileCreateAndCopyStream(writePath, theEntry.Size, s);
                            File.SetLastWriteTime(writePath, lastWriteTime);

                            System.Text.StringBuilder thumbnailCachePathBuilder = new System.Text.StringBuilder(zipFileName);
                            if (directoryName.Length > 0) {
                                thumbnailCachePathBuilder.Append(Path.DirectorySeparatorChar);
                                thumbnailCachePathBuilder.Append(directoryName);
                            }
                            thumbnailCachePathBuilder.Append(Path.DirectorySeparatorChar);
                            thumbnailCachePathBuilder.Append(fileName);
                            string thumbnailCachePath = thumbnailCachePathBuilder.ToString();

                            if (null != progressReportAndQueryCancel && progressReportAndQueryCancel(writePath, thumbnailCachePath, fileLength, (int)(100 * s.Position / fileSize))) {
                                result = false;
                                break;
                            }
                        }
                    }
                } catch (ICSharpCode.SharpZipLib.SharpZipBaseException) {
                    result = false;
                } catch (IOException) {
                    result = false;
                } catch (System.NotSupportedException) {
                    result = false;
                } catch (InvalidDataException) {
                    result = false;
                } catch (System.ArgumentException) {
                    result = false;
                } catch (System.UnauthorizedAccessException) {
                    result = false;
                } catch (System.IndexOutOfRangeException) {
                    result = false;
                } catch (System.NullReferenceException) {
                    result = false;
                }
            } else if (zipFileName.EndsWith(".lzh", true, CultureInfo.CurrentCulture)) {
                try {
                    System.DateTime lastWriteTime = File.GetLastWriteTime(zipFileName);
                    using (LhaUtil.LhaInputStream s =
                        new LhaUtil.LhaInputStream(File.OpenRead(zipFileName))) {
                        LhaUtil.LhaEntry theEntry;
                        while ((theEntry = s.NextEntry()) != null) {
                            string directoryName = theEntry.DirectoryName;
                            System.Text.StringBuilder writeFolderNameBuilder = new System.Text.StringBuilder(toFolderName);
                            if (directoryName.Length > 0) {
                                writeFolderNameBuilder.Append(Path.DirectorySeparatorChar);
                                writeFolderNameBuilder.Append(directoryName);
                            }
                            string writeFolderName = writeFolderNameBuilder.ToString();
                            if (!Directory.Exists(writeFolderName)) {
                                Directory.CreateDirectory(writeFolderName);
                                ++nFolders;
                            }
                            if (!bUnzipFile) {
                                continue;
                            }
                            string fileName = theEntry.FileName;
                            if (0 == fileName.Length) {
                                continue;
                            }
                            string writePath = writeFolderName + Path.DirectorySeparatorChar + fileName;

                            long fileLength = FileCreateAndCopyStream(writePath, theEntry.OriginalDataSize, s);
                            File.SetLastWriteTime(writePath, lastWriteTime);

                            System.Text.StringBuilder thumbnailCachePathBuilder = new System.Text.StringBuilder(zipFileName);
                            if (directoryName.Length > 0) {
                                thumbnailCachePathBuilder.Append(Path.DirectorySeparatorChar);
                                thumbnailCachePathBuilder.Append(directoryName);
                            }
                            thumbnailCachePathBuilder.Append(Path.DirectorySeparatorChar);
                            thumbnailCachePathBuilder.Append(fileName);
                            string thumbnailCachePath = thumbnailCachePathBuilder.ToString();
                            if (null != progressReportAndQueryCancel && progressReportAndQueryCancel(writePath, thumbnailCachePath, fileLength, (int)(100 * s.Position / s.Length))) {
                                result = false;
                                break;
                            }
                        }
                    }
                } catch (InvalidDataException) {
                    result = false;
                } catch (System.NotSupportedException) {
                    result = false;
                } catch (IOException) {
                    result = false;
                } catch (System.ArgumentException) {
                    result = false;
                } catch (System.UnauthorizedAccessException) {
                    result = false;
                }
            }
            if (!bUnzipFile) {
                return 0 != nFolders;
            }
            return result;
        }

        private static bool UnzipAllFoldersToFolder(string zipFileName, string toFolderName)
        {
            return UnzipAllFilesToFolder(zipFileName, toFolderName, false);
        }

        private static bool UnzipAllFilesToFolder(string zipFileName, string toFolderName)
        {
            return UnzipAllFilesToFolder(zipFileName, toFolderName, true);
        }

    }
}
